/* Copyright (C) 2013-2021 TU Dortmund
 * This file is part of LearnLib, http://www.learnlib.de/.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.learnlib.examples;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Random;

import de.learnlib.algorithms.lstar.dfa.ClassicLStarDFA;
import de.learnlib.algorithms.lstar.dfa.ClassicLStarDFABuilder;
import de.learnlib.algorithms.rivestschapire.RivestSchapireDFA;
import de.learnlib.algorithms.rivestschapire.RivestSchapireDFABuilder;
import de.learnlib.algorithms.lstar.dfa.ShahbazDFA;
import de.learnlib.algorithms.lstar.dfa.ShahbazDFABuilder;
import de.learnlib.api.algorithm.LearningAlgorithm;
import de.learnlib.api.oracle.MembershipOracle.DFAMembershipOracle;
import de.learnlib.counterexamples.LocalSuffixFinders;
import de.learnlib.datastructure.observationtable.OTUtils;
import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter;
import de.learnlib.filter.cache.dfa.DFACaches;
import de.learnlib.filter.statistic.oracle.DFACounterOracle;
import de.learnlib.oracle.equivalence.DFARandomWordsEQOracle;
import de.learnlib.oracle.equivalence.DFAWMethodEQOracle;
import de.learnlib.oracle.membership.SimulatorOracle.DFASimulatorOracle;
import de.learnlib.util.Experiment.DFAExperiment;
import de.learnlib.util.statistics.SimpleProfiler;
import net.automatalib.automata.fsa.DFA;
import net.automatalib.automata.fsa.impl.compact.CompactDFA;
import net.automatalib.incremental.dfa.tree.IncrementalDFATreeBuilder;
import net.automatalib.serialization.dot.GraphDOT;
import net.automatalib.serialization.fsm.parser.FSM2DFAParser;
import net.automatalib.util.automata.builders.AutomatonBuilders;
import net.automatalib.util.automata.random.RandomAutomata;
import net.automatalib.visualization.Visualization;
import net.automatalib.words.Alphabet;
import net.automatalib.words.impl.Alphabets;
import de.learnlib.algorithms.discriminationtree.dfa.DTLearnerDFA;
import de.learnlib.algorithms.discriminationtree.dfa.DTLearnerDFABuilder;
import de.learnlib.filter.cache.dfa.DFACacheOracle;
import de.learnlib.filter.cache.dfa.DFACaches;
import net.automatalib.incremental.dfa.tree.IncrementalDFATreeBuilder;
import de.learnlib.oracle.equivalence.DFARandomWordsEQOracle;
import de.learnlib.oracle.equivalence.DFARandomWMethodEQOracle;
import net.automatalib.util.automata.random.RandomAutomata;
/**
 * This example shows the usage of a learning algorithm and an equivalence test as part of an experiment in order to
 * learn a simulated SUL (system under learning).
 *
 * @author falkhowar
 */
@SuppressWarnings("PMD.SystemPrintln")
public final class Example1 {

    private static final int EXPLORATION_DEPTH = 4;

    private Example1() {
        // prevent instantiation
    }

    public static void main(String[] args) throws IOException {

        // load DFA and alphabet
        final int size  = 100;
        CompactDFA<Integer> target = constructSUL(10,size);
        Alphabet<Integer> inputs = target.getInputAlphabet();

        // construct a simulator membership query oracle
        // input  - I (determined by example)
        DFAMembershipOracle<Integer> sul = new DFASimulatorOracle<>(target);
        // oracle for counting queries wraps SUL
        DFACounterOracle<Integer> mqOracle = new DFACounterOracle<>(sul, "membership queries");
        DFACacheOracle<Integer> CacheOracle = DFACaches.createCache(inputs, mqOracle);
        // construct L* instance
        ClassicLStarDFA<Integer> lstar =
                new ClassicLStarDFABuilder<Integer>().withAlphabet(inputs) // input alphabet
                                                       .withOracle(CacheOracle) // membership oracle
                                                       .create();
        /*
        ShahbazDFA<Integer> lstar =
                new ShahbazDFABuilder<Integer>().withAlphabet(inputs) // input alphabet
                .withOracle(CacheOracle) // membership oracle
                .create();
        DTLearnerDFA<Integer> lstar=
                new DTLearnerDFABuilder<Integer>().withAlphabet(inputs) // input alphabet
                .withOracle(CacheOracle) // membership oracle
                .withSuffixFinder(LocalSuffixFinders.RIVEST_SCHAPIRE)
                .create();
        RivestSchapireDFA<Integer> lstar = new RivestSchapireDFABuilder<I>().withAlphabet(inputs) // input alphabet
                .withOracle(CacheOracle) // membership oracle
                .create();*/
        // construct a W-method conformance test
        // exploring the system up to depth 4 from
        // every state of a hypothesis
        DFAWMethodEQOracle<Integer> wMethod = new DFAWMethodEQOracle<>(CacheOracle,0,size);
        //DFARandomWMethodEQOracle<I> wMethod = new DFARandomWMethodEQOracle<>(CacheOracle, 0,6);
        //DFARandomWordsEQOracle<I> wMethod = new DFARandomWordsEQOracle<>(CacheOracle, 0,10,200);
        // construct a learning experiment from
        // the learning algorithm and the conformance test.
        // The experiment will execute the main loop of
        // active learning
        DFAExperiment<Integer> experiment = new DFAExperiment<>(lstar, wMethod, inputs);

        // turn on time profiling
        experiment.setProfile(true);

        // enable logging of models
        experiment.setLogModels(true);

        // run experiment
        experiment.run();

        // get learned model
        DFA<?, Integer> result = experiment.getFinalHypothesis();

        // report results
        System.out.println("-------------------------------------------------------");

        // profiling
        System.out.println(SimpleProfiler.getResults());

        // learning statistics
        System.out.println(experiment.getRounds().getSummary());
        System.out.println(mqOracle.getStatisticalData().getSummary());

        // model statistics
        System.out.println("States: " + result.size());
        System.out.println("Sigma: " + inputs.size());

        // show model
        System.out.println();
        System.out.println("Model: ");
        GraphDOT.write(result, inputs, System.out); // may throw IOException!

        Visualization.visualize(result, inputs);

        System.out.println("-------------------------------------------------------");

        System.out.println("Final observation table:");
        new ObservationTableASCIIWriter<>().write(lstar.getObservationTable(), System.out);

        OTUtils.displayHTMLInBrowser(lstar.getObservationTable());
    }

    /**
     * creates example from Angluin's seminal paper.
     *
     * @return example dfa
     */
    private static CompactDFA<Integer> constructSUL(int numInputs, int size) {
        // input alphabet contains Is 'a'..'b'
        //Alphabet<I> sigma = Alphabets.Is('a', 'b');

        // @formatter:off
        // create automaton
        /*return AutomatonBuilders.newDFA(sigma)
                .withInitial("q0")
                .from("q0")
                    .on('a').to("q1")
                    .on('b').to("q0")
                .from("q1")
                    .on('a').to("q2")
                    .on('b').to("q1")
                .from("q2")
                    .on('a').to("q3")
                    .on('b').to("q2")
                .from("q3")
                    .on('a').to("q0")
                    .on('b').to("q3")
                .withAccepting("q3")
                .create();*/
        /*return AutomatonBuilders.newDFA(sigma)
                .withInitial("q0")
                .from("q0")
                    .on('a').to("q1")
                    .on('b').to("q3")
                .from("q1")
                    .on('a').to("q2")
                    .on('b').to("q4")
                .from("q2")
                    .on('a').to("q2")
                    .on('b').to("q5")
                .from("q3")
                    .on('a').to("q4")
                    .on('b').to("q0")
                .from("q4")
                    .on('a').to("q5")
                    .on('b').to("q1")
                .from("q5")
                    .on('a').to("q5")
                    .on('b').to("q2")
                .withAccepting("q5")
                .create();*/
        // @formatter:on
        CompactDFA<Integer> randomDfa = RandomAutomata.randomDFA(new Random(), size, Alphabets.integers(0, numInputs - 1));


        //Visualization.visualize(randomDfa, Alphabets.integers(0, numInputs - 1));
        return randomDfa;
    }

}
